* Intro

This is a simulation tool for moves in the board game risk. Its approach is to run a simulation as many times as specified and then calculate probabilities by counting results of those simulations. It offers generic procedures to facilitate running simulations.

The rules of a risk game are usually as follows:

1. attacker may use up to 3 dice, but may choose to use fewer dice
2. defender may use up to 2 dice and cannot choose to use fewer than that
3. on equal eye count on the dice, the defender wins

However, this program allows modification of the rules. Some procedures allow specifying non-standard rules, which they give to other procedures in the process of running the simulations, so that the whole calculation is based on those modified rules.

The program makes use of a random number generator (RNG), which outputs uniformly distributed random numbers. It is possible to roll dice with more than 6 sides, but currently this is not used by any simulation procedure.

* Terminology

- die :: n-sided die
- dice :: more than one n-sided die
- round :: rolling dice for attackers and defenders once
- fight :: rolling dice for as many times as it takes for the attacker or defender to be unable to roll dice
- battle :: multiple subsequent fights, each with remaining attackers and defenders after the previous fight
- annihilation :: rolling dice, playing rounds, until the attacker cannot roll any die any longer or won the fight.

* Code conventions

- The attacker count of dice should always be first and the defender count of dice behind in arguments to procedures.

* Usage

** Creating the random number generator

You can create a random number generator with an optionally given seed as follows:

#+BEGIN_SRC scheme
(make-random-integer-generator #:seed 12345)
#+END_SRC

Setting a seed will make results reproducible.

** Creating rules

You can create game rules as follows:

#+BEGIN_SRC scheme
(make-risk-rules att-dice def-dice eq-eyes-win)
#+END_SRC

Where ~eq-eyes-win~ is a symbol, one of the following:

- ~'att~: meaning the attacker wins, if eye count is equal
- ~'def~: meaning the defender wins, if eye count is equal
- ~'none~: meaning no one loses units, if the eye count is equal
- ~'both~: meaning both players lose units, if the eye count is equal

** Calculating rounds

There is a procedure named ~simulate-round~, which rolls dice for a single round of risk. Here is an example for running the simulation once using default risk game rules:

#+BEGIN_SRC scheme
(simulate-round
 (make-init-fight-situation 4 3)
 (make-random-integer-generator #:seed 12345)
 #:rules *default-risk-rules*)
#+END_SRC

You can run this simulation an arbitrary number of times, collecting a result table, which counts the outcomes as follows:

#+BEGIN_SRC scheme
(display-summary
 (try-n-times (make-init-fight-situation 4 3)
              10000
              simulate-round
              (make-random-integer-generator)
              #:rules *default-risk-rules*)
 #:fraction-formatter (λ (frac)
                        (format-fraction frac #:precision 3)))
#+END_SRC

The procedure ~try-n-times~ will run the simulation ~10000~ times and the results will be counted in a resulting table. That table is summarized and by ~display-summary~, which counts occurences of unique round results, calculates their probability and displays that.

** Calculating fights (annihilation)

Simulation of annihilation works the same way as simulating single rounds:

#+BEGIN_SRC scheme
(simulate-fight
 (make-init-fight-situation 10 7)
 (make-random-integer-generator #:seed 12345)
 #:rules *default-risk-rules*)
#+END_SRC

As does simulation of multiple annihilations:

#+BEGIN_SRC scheme
(display-summary
 (try-n-times (make-init-fight-situation 10 7)
              10000
              simulate-fight
              (make-random-integer-generator #:seed 12345)
              #:rules *default-risk-rules*))
#+END_SRC

** Calculating battle

Simulating battles can be done as follows:

#+BEGIN_SRC scheme
(displayln
 (format-fraction
  (calc-att-winning-prob
   (let ([rand-int-gen (make-random-integer-generator #:seed 12345)])
     (try-n-times-general
      (lambda ()
        (simulate-battle 8
                         '(3 2 1)
                         rand-int-gen
                         #:rules *default-risk-rules*))
      100000)))
  #:precision 5))
#+END_SRC

There is also another procedure, which results in a battle win probability instead of a large table of all occurring courses of the battle and internally repeats fights a given number of times, instead of repeating the battle a given number of times:

#+BEGIN_SRC scheme
(displayln
 "Probability of winning with 9 against 1, 1, 10:"
 (format-fraction
  (calc-battle-att-win-prob 9
                            '(1 1 10)
                            10000
                            (make-random-integer-generator #:seed 12345)
                            #:rules *default-risk-rules*)
  #:precision 5))
#+END_SRC

If you want to make assumptions about outcomes of fights of a battle, you can use another procedure as follows:

#+BEGIN_SRC scheme
(displayln
 (format-fraction
  (calc-consecutive-att-win-prob '(9 8 7)
                                 '(1 1 10)
                                 10000
                                 (make-random-integer-generator #:seed 12345)
                                 #:rules *default-risk-rules*)
  #:precision 5))
#+END_SRC

** Calculating win probability for attacker or defender

Given a result table containing fight results, you can quickly calculate the winning probability for attacker or defender as follows:

#+BEGIN_SRC scheme
(displayln
 (format-fraction
  (calc-att-winning-prob
   (try-n-times (make-init-fight-situation 10 7)
                10000
                simulate-fight
                (make-random-integer-generator #:seed 12345)
                #:rules *default-risk-rules*))
  #:precision 5))
#+END_SRC

There is a convenience procedure for calculating defender winning probability as well:

#+BEGIN_SRC scheme
(displayln
 (format-fraction
  (calc-def-win-prob
   (try-n-times (make-init-fight-situation 10 7)
                10000
                simulate-fight
                (make-random-integer-generator #:seed 12345)
                #:rules *default-risk-rules*))
  #:precision 5))
#+END_SRC

* To do [6/8]

- [X] ~calc-battle-att-win-prob~ is strange in the sense, that it internally performs ~n~ simulations, instead of being wrapped like the other procedures with ~try-n-times~. It should be refactored to simulate the battle only once and then be simulated ~n~ times via the ~try-n-times~ procedure. – Or should it not?
- [X] rename: ~annihilate~ -> ~simulate-fight~
- [X] rename: ~try-single-round~ -> ~simulate-round~
- [X] make ~att-lost?~ take rules as an argument as well
- [X] make ~def-lost?~ take rules as an argument as well
- [X] ~display-summary~ should take a formatter procedure for formatting the fractions, instead of a precision and assuming, that the user wants to see decimal numbers. Giving a formatter procedure makes no assumptions about what the user wants.
- [ ] Add more rules and the logic to calculate according to them.
  - [ ] Add number of sides of a die to the rules struct, so that different dice can be used.
  - [ ] Rules and rules interpretation in the program could be modified, so that the attacker needs to have not only 1 more eye on a die, but any number of eyes more than the defender, to win.
- [ ] improve ~try-n-times~
  1. [ ] use only ~try-n-times-general~ instead of ~try-n-times~
  2. [ ] remove ~try-n-times~
  3. [ ] rename: ~try-n-times-general~ -> ~try-n-times~
